package cn.shop.front.service.impl;

import cn.dev33.satoken.dao.SaTokenDaoRedisJackson;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.net.NetUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.shop.front.config.HxPayConfig;
import cn.shop.front.config.FkPayConfig;
import cn.shop.front.config.PayConfig;
import cn.shop.front.entity.order.ServiceCharge;
import cn.shop.front.entity.req.ScanTransactionBatchReq;
import cn.shop.front.entity.req.TransactionBatchReq;
import cn.shop.front.mapper.FrontUserMapper;
import cn.shop.front.service.BackPaymentChannelService;
import cn.shop.front.service.BackSettingService;
import cn.shop.front.service.OrderFrozenAmountService;
import cn.shop.front.service.TransactionRecordService;
import cn.shop.front.service.message.MessageUtil;
import cn.shop.front.util.AliasUtil;
import cn.shop.front.util.CurrentUserUtil;
import cn.shop.front.util.IpUtil;
import cn.shop.msgs.OrderMsg;
import cn.shop.transcation.TransactionRecord.entity.RechargeInfoReq;
import cn.shop.transcation.TransactionRecord.entity.ScanTransactionRecord;
import cn.shop.transcation.TransactionRecord.entity.TransactionRecord;
import cn.shop.transcation.backPaymentChannelModule.entity.BackPaymentChannel;
import cn.shop.transcation.backSetting.entity.BackSetting;
import cn.shop.transcation.detail.entity.FundDetail;
import cn.shop.transcation.detail.entity.TransactionDetail;
import cn.shop.transcation.detail.entity.TransactionRecordQueryReq;
import cn.shop.transcation.detail.mapper.FundDetailMapper;
import cn.shop.transcation.frontUserModule.FrontUser;
import cn.shop.transcation.merchantRateModule.entity.MerchantRate;
import cn.shop.transcation.merchantRateModule.mapper.MerchantRateMapper;
import cn.shop.transcation.merchantUser.mapper.MerchantUserMapper;
import cn.shop.transcation.merchantUser.mapper.entity.MerchantUser;
import cn.shop.transcation.order.entity.Order;
import cn.shop.transcation.order.entity.OrderFrozenAmount;
import cn.shop.transcation.order.entity.OrderRate;
import cn.shop.transcation.order.mapper.OrderMapper;
import cn.shop.transcation.order.mapper.OrderRateMapper;
import cn.shop.transcation.rechargerecord.entity.Record;
import cn.shop.transcation.rechargerecord.mapper.RecordMapper;
import cn.shop.utils.*;
import cn.shop.utils.constant.SystemSettingConstant;
import cn.shop.utils.enums.OrderStatus;
import cn.shop.utils.enums.PayApiType;
import cn.shop.utils.enums.RechargeSolution;
import cn.shop.utils.enums.TransactionState;
import cn.shop.utils.exception.ServiceException;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 交易记录(TransactionRecord)表服务实现类
 *
 * @author jzw
 * @since 2021-12-13 10:49:41
 */
@Service
@Slf4j
public class TransactionRecordServiceImpl implements TransactionRecordService {

	@Resource
	private PayConfig payConfig;
	@Resource
	private HxPayConfig hxPayConfig;
	@Resource
	private FkPayConfig fkPayConfig;

	@Resource
	private OrderMapper orderMapper;

	@Resource
	private MerchantUserMapper merchantUserMapper;
	@Resource
	private MerchantRateMapper merchantRateMapper;

	@Resource
	private OrderRateMapper orderRateMapper;

	@Resource
	private FrontUserMapper frontUserMapper;

	@Resource
	private RecordMapper recordMapper;

	@Resource
	private FundDetailMapper fundDetailMapper;

	@Resource
	private RedissonClient redissonClient;

	@Resource
	private SaTokenDaoRedisJackson redisJackson;

	@Resource
	private MessageUtil messageUtil;

	@Resource
	private BackSettingService backSettingService;
	@Resource
	private OrderFrozenAmountService orderFrozenAmountService;
	@Resource
	private BackPaymentChannelService channelService;

	@Value("${schema}")
	private String origin;

	/**
	 * 创建订单记录
	 *
	 * @param transactionRecord 购买信息
	 * @return 返回结果
	 * @author jzw
	 * @since 2021/12/13 11:10
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean createRecord(long userId, TransactionRecord transactionRecord, HttpServletRequest request) {
		String domain = AliasUtil.getAlias(request);
		Order order = checkOrder(transactionRecord.getProductId(), userId);
		RLock lock = redissonClient.getLock("pay-" + transactionRecord.getProductId().toString());
		try {
			if (lock.tryLock(0, 60, TimeUnit.SECONDS)) {
				//校验商品是否为可买状态
				LocalDateTime now = LocalDateTime.now();
				Duration duration = Duration.between(now, order.getDownTime());
				//如果当前离下架时间不足2秒，则购买失败
				if (duration.getSeconds() < 2) {
					throw new ServiceException("当前商品已下架");
				}
				//校验账户余额是否充足
				FrontUser buyer = frontUserMapper.selectById(userId);
				FrontUser seller = frontUserMapper.selectById(order.getSellerId());
				if (NumberUtil.isLess(buyer.getBalance(), order.getAmount())) {
					throw new ServiceException("账户余额不足，请充值或选用其他支付方式");
				}
				// 玩家减去余额
				LambdaUpdateWrapper<FrontUser> updateUserWrapper = Wrappers.lambdaUpdate();
				updateUserWrapper.setSql("balance = balance - " + order.getAmount()).
						eq(FrontUser::getId, buyer.getId());
				boolean res = SqlHelper.retBool(frontUserMapper.update(buyer, updateUserWrapper));
				if (!res) {
					throw new ServiceException(StrUtil.format("玩家购买商品失败,扣余额出错,商品id:{},玩家id:{}", order.getId(), buyer.getId()));
				}
				Order update = new Order();
				update.setStatus(OrderStatus.COMPLETED);
				update.setBuyerId(userId);
				update.setBuyerAccount(transactionRecord.getAccount());
				update.setDealTime(LocalDateTime.now());
				update.setBuyerIp(IpUtil.getClientIp());
				update.setId(order.getId());
				order.setBuyerIp(update.getBuyerIp());
				orderMapper.updateById(update);
				processOrder(order, "余额支付", buyer, seller, BigDecimal.ZERO, domain, false);
				return true;
			}
			else {
				throw new ServiceException("当前商品已锁定");
			}
		} catch (Exception e) {
			log.warn("余额购买异常", e);
			throw new ServiceException("购买商品失败");
		} finally {
			if (lock.isLocked() && lock.isHeldByCurrentThread()) {
				lock.unlock();
			}
		}
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public List<Order> createRecord(long userId, TransactionBatchReq record, HttpServletRequest request) {
		String domain = AliasUtil.getAlias(request);
		List<Order> orders = checkOrder(record.getProductIds(), userId);
		BigDecimal amount = orders.stream().map(Order::getAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
		FrontUser buyer = frontUserMapper.selectById(userId);
		if (NumberUtil.isLess(buyer.getBalance(), amount)) {
			throw new ServiceException("账户余额不足，请充值或选用其他支付方式");
		}
		Map<Long, RLock> locks = new HashMap<>();
		for (Order order : orders) {
			locks.put(order.getId(), redissonClient.getLock("pay-" + order.getId().toString()));
		}
		List<Order> updates = new ArrayList<>();
		String ip = IpUtil.getClientIp();
		for (Order order : orders) {
			RLock lock = locks.get(order.getId());
			try {
				if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
					//校验商品是否为可买状态
					LocalDateTime now = LocalDateTime.now();
					Duration duration = Duration.between(now, order.getDownTime());
					//如果当前离下架时间不足2秒，则购买失败
					if (duration.getSeconds() < 2) {
						//throw new ServiceException("当前商品已下架");
						log.info("{} 已经下架", order);
						continue;
					}
					order.setStatus(OrderStatus.COMPLETED);
					order.setBuyerId(userId);
					order.setBuyerAccount(record.getAccount());
					order.setDealTime(LocalDateTime.now());
					order.setBuyerIp(ip);
					order.setId(order.getId());
					updates.add(order);
				}
				else {
					log.info("{} 已经被锁定", order);
				}
			} catch (Exception ignored) {}
		}

		try {
			amount = updates.stream().map(Order::getAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
			LambdaUpdateWrapper<FrontUser> updateUserWrapper = Wrappers.lambdaUpdate();
			updateUserWrapper.setSql("balance = balance - " + amount).
					eq(FrontUser::getId, buyer.getId());
			boolean res = SqlHelper.retBool(frontUserMapper.update(buyer, updateUserWrapper));
			if (!res) {
				throw new ServiceException(StrUtil.format("玩家批量购买商品失败,扣余额出错,商品{},玩家id:{}", updates, buyer.getId()));
			}
			for (Order order : updates) {
				orderMapper.updateById(order);
				FrontUser seller = frontUserMapper.selectById(order.getSellerId());
				processOrder(order, "余额支付", buyer, seller, BigDecimal.ZERO, domain, false);
			}
			return updates;
		} catch (Exception e) {
			log.warn("余额购买异常", e);
			throw new ServiceException("购买商品失败");
		} finally {
			for (RLock lock : locks.values()) {
				if (lock.isLocked() && lock.isHeldByCurrentThread()) {
					lock.unlock();
				}
			}
		}
	}

	/**
	 * 创建订单调用第三方支付
	 *
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Object createRecord(long userId, ScanTransactionRecord record, HttpServletRequest request) {
		String scheme = request.getHeader("origin");
		Order order = checkOrder(record.getProductId(), userId);
		BackPaymentChannel channel = channelService.getById(record.getChannelId());
		if (channel == null || channel.getType() == null
				|| StrUtil.hasBlank(channel.getSecret())
				|| channel.getMerchantNumber() == null) {
			throw new ServiceException("支付方式错误");
		}
		BigDecimal rate = BigDecimal.ZERO;
		if (channel.getRate() != null) {
			BigDecimal charges = channel.getRate().divide(new BigDecimal(100), 4, RoundingMode.CEILING);
			if (charges.compareTo(BigDecimal.ZERO) > 0) {
				rate = order.getAmount().multiply(charges);
			}
		}
		RLock lock = redissonClient.getLock("pay-" + record.getProductId().toString());
		String domain = AliasUtil.getAlias(request);
		try {
			if (lock.tryLock(0, 20, TimeUnit.SECONDS)) {
				//校验商品是否为可买状态
				LocalDateTime now = LocalDateTime.now();
				Duration duration = Duration.between(now, order.getDownTime());
				//如果当前离下架时间不足2秒，则购买失败
				if (duration.getSeconds() < 2) {
					throw new ServiceException("当前商品已下架");
				}
				//修改订单状态
				Order update = new Order();
				update.setId(order.getId());
				update.setStatus(OrderStatus.TRADING);
				update.setBuyerId(userId);
				update.setBuyerIp(IpUtil.getClientIp());
				update.setBuyerAccount(record.getAccount());
				update.setDealTime(LocalDateTime.now());
				orderMapper.updateById(update);
				String notifyUrl;
				switch (channel.getApiType()) {
					case DY_PAYMENT:
						notifyUrl = payConfig.getOrderUrl()
								.replace("{origin}", scheme)
								//.replace("{host}", host)
								.replace("{channelId}", String.valueOf(channel.getId()))
								.replace("{alias}", domain);
						return sendPay(order.getAmount().add(rate), order.getId(), NetUtil.ipv4ToLong(update.getBuyerIp()), channel,
								record.getReturnUrl(), notifyUrl, record.getBankCode());
					case HX_PAYMENT:
						notifyUrl = hxPayConfig.getOrderUrl()
								.replace("{origin}", scheme)
								//.replace("{host}", host)
								.replace("{channelId}", String.valueOf(channel.getId()))
								.replace("{alias}", domain);
						return sendPay(order.getAmount().add(rate), order.getId(), NetUtil.ipv4ToLong(update.getBuyerIp()), channel,
								record.getReturnUrl(), notifyUrl, record.getBankCode());
					case FK_PAYMENT:
						notifyUrl = fkPayConfig.getOrderUrl()
								.replace("{origin}", scheme)
								//.replace("{host}", host)
								.replace("{channelId}", String.valueOf(channel.getId()))
								.replace("{alias}", domain);
						return sendPay(order.getAmount().add(rate), order.getId(), update.getBuyerIp(), channel,
								record.getReturnUrl(), notifyUrl, record.getBankCode());
					default:
						throw new ServiceException("支付渠道错误");
				}
			}
			else {
				throw new ServiceException("当前商品已锁定");
			}
		} catch (Exception e) {
			log.warn("扫码支付购买异常", e);
			throw new ServiceException("扫码支付错误");
		} finally {
			if (lock.isLocked() && lock.isHeldByCurrentThread()) {
				lock.unlock();
			}
		}
	}

	/**
	 * 批量第三方支付
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Object createRecord(long userId, ScanTransactionBatchReq record, HttpServletRequest request) {
		String scheme = request.getHeader("origin");
		BackPaymentChannel channel = channelService.getById(record.getChannelId());
		if (channel == null || channel.getType() == null
				|| StrUtil.hasBlank(channel.getSecret())
				|| channel.getMerchantNumber() == null) {
			throw new ServiceException("支付方式错误");
		}
		List<Order> orders = checkOrder(record.getProductIds(), userId);
		BigDecimal amount = orders.stream().map(Order::getAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
		BigDecimal rate = BigDecimal.ZERO;
		if (channel.getRate() != null) {
			BigDecimal charges = channel.getRate().divide(new BigDecimal(100), 4, RoundingMode.CEILING);
			if (charges.compareTo(BigDecimal.ZERO) > 0) {
				rate = amount.multiply(charges);
			}
		}
		Map<Long, RLock> locks = new HashMap<>();
		for (Order order : orders) {
			locks.put(order.getId(), redissonClient.getLock("pay-" + order.getId().toString()));
		}
		String ip = IpUtil.getClientIp();
		List<Order> updates = new ArrayList<>();
		String domain = AliasUtil.getAlias(request);
		for (Order order : orders) {
			RLock lock = locks.get(order.getId());
			try {
				if (lock.tryLock(0, 30, TimeUnit.SECONDS)) {
					//校验商品是否为可买状态
					LocalDateTime now = LocalDateTime.now();
					Duration duration = Duration.between(now, order.getDownTime());
					//如果当前离下架时间不足2秒，则购买失败
					if (duration.getSeconds() < 2) {
						log.info("{} 已经下架", order);
						continue;
					}
					//修改订单状态
					order.setStatus(OrderStatus.TRADING);
					order.setBuyerId(userId);
					order.setBuyerIp(ip);
					order.setBuyerAccount(record.getAccount());
					order.setDealTime(LocalDateTime.now());
					updates.add(order);
				}
				else {
					log.info("{} 已经被锁定", order);
				}
			} catch (Exception e) {
				log.warn("扫码支付购买异常", e);
			} finally {
				if (lock.isLocked() && lock.isHeldByCurrentThread()) {
					lock.unlock();
				}
			}
		}
		long currentTime = System.currentTimeMillis();
		try {
			amount = updates.stream().map(Order::getAmount).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
			String ids = updates.stream().map(Order::getId).map(String::valueOf).collect(Collectors.joining(","));
			for (Order update : updates) {
				orderMapper.updateById(update);
			}
			RBucket<String> bucket = redissonClient.getBucket("buy:" + currentTime);
			bucket.set(ids, 5, TimeUnit.MINUTES);
			String notifyUrl;
			String returnUrl = record.getReturnUrl();
			if (updates.size() < record.getProductIds().size()) {
				returnUrl += "?isFull=false";
			} else {
				returnUrl += "?isFull=true";
			}
			switch (channel.getApiType()) {
				case DY_PAYMENT:
					notifyUrl = payConfig.getBatchOrderUrl()
							.replace("{origin}", scheme)
							//.replace("{host}", host)
							.replace("{channelId}", String.valueOf(channel.getId()))
							.replace("{alias}", domain);
					return sendPay(amount.add(rate), currentTime, NetUtil.ipv4ToLong(ip), channel,
							returnUrl, notifyUrl, record.getBankCode());
				case HX_PAYMENT:
					notifyUrl = hxPayConfig.getBatchOrderUrl()
							.replace("{origin}", scheme)
							//.replace("{host}", host)
							.replace("{channelId}", String.valueOf(channel.getId()))
							.replace("{alias}", domain);
					return sendPay(amount.add(rate), currentTime, NetUtil.ipv4ToLong(ip), channel,
							returnUrl, notifyUrl, record.getBankCode());
				case FK_PAYMENT:
					notifyUrl = fkPayConfig.getBatchOrderUrl()
							.replace("{origin}", scheme)
							//.replace("{host}", host)
							.replace("{channelId}", String.valueOf(channel.getId()))
							.replace("{alias}", domain);
					return sendPay(amount.add(rate), currentTime, ip, channel,
							returnUrl, notifyUrl, record.getBankCode());
				default:
					throw new ServiceException("支付渠道错误");
			}
		} catch (Exception e) {
			log.warn("扫码支付购买异常", e);
			throw new ServiceException("扫码支付错误");
		} finally {
			for (RLock lock : locks.values()) {
				if (lock.isLocked() && lock.isHeldByCurrentThread()) {
					lock.unlock();
				}
			}
		}
	}

	/**
	 * 充值
	 *
	 * @param rechargeInfoReq 充值参数
	 * @return 返回结果
	 * @author jzw
	 * @since 2022/1/11 10:31
	 */
	@Override
	@Transactional(rollbackFor = Exception.class)
	public Object recharge(RechargeInfoReq rechargeInfoReq, HttpServletRequest request) {
		String scheme = request.getHeader("origin");
		String ipStr = IpUtil.getClientIp();

		FrontUser frontUser = frontUserMapper.selectById(CurrentUserUtil.getUserId());
		BackPaymentChannel channel = channelService.getById(rechargeInfoReq.getRechargeSolution());
		if (channel == null || channel.getType() == null
				|| StrUtil.hasBlank(channel.getSecret())
				|| channel.getMerchantNumber() == null) {
			throw new ServiceException("支付方式错误");
		}
		BigDecimal rate = BigDecimal.ZERO;
		BigDecimal amount = Convert.toBigDecimal(rechargeInfoReq.getAmount());
		if (channel.getRate() != null) {
			BigDecimal charges = channel.getRate().divide(new BigDecimal(100), 4, RoundingMode.CEILING);
			if (charges.compareTo(BigDecimal.ZERO) > 0) {
				rate = amount.multiply(charges);
			}
		}
		String domain = AliasUtil.getAlias(request);
		Long merchantId = fundDetailMapper.getMerchantIdByAlias(domain);
		Record record = new Record();
		record.setState(TransactionState.PENDING_PAYMENT);
		record.setPlayerName(frontUser.getAccount());
		record.setPlayerId(frontUser.getId());
		record.setAmount(amount);
		record.setActualAmount(amount.subtract(rate));
		record.setBalance(frontUser.getBalance().add(record.getActualAmount()));
		record.setChannel(channel.getType().getDescription() + "#" + channel.getName());
		record.setIp(ipStr);
		record.setUid(merchantId != null ? merchantId : 0L);
		record.setChannelId(channel.getId());

		boolean insert = SqlHelper.retBool(recordMapper.insert(record));
		if (insert) {
			StringRedisTemplate stringRedisTemplate = redisJackson.stringRedisTemplate;
			//通过redis 的过期监听机制 过期后触发 自动取消订单
			stringRedisTemplate.opsForValue().set("orderTR-" + record.getId(), "0", 15, TimeUnit.MINUTES);
			log.info("开始设置充值订单 {} 的redis超时key：{}", record.getId(), "orderTR-" + record.getId());
			if (channel.getChannel() == 0) {
				return pcChannel(channel, scheme, domain, amount, record, rechargeInfoReq, ipStr);
			} else {
				log.info("app请求：{}", request.getScheme());
				return appChannel(channel, StrUtil.blankToDefault(scheme, origin), domain, amount, record, rechargeInfoReq, ipStr);
			}
		} else {
			throw new ServiceException("服务器异常,暂时无法交易");
		}
	}

	@Override
	public Record getRecord(long id) {
		return recordMapper.selectById(id);
	}

	private Object pcChannel(BackPaymentChannel channel, String scheme, String domain, BigDecimal amount, Record record, RechargeInfoReq rechargeInfoReq, String ipStr) {
		long ip = NetUtil.ipv4ToLong(ipStr);
		String notifyUrl;
		switch (channel.getApiType()) {
			case DY_PAYMENT:
				notifyUrl = payConfig.getNotifyUrl()
						.replace("{origin}", scheme)
						.replace("{alias}", domain);
				return sendPay(amount, record.getId(), ip, channel, rechargeInfoReq.getReturnUrl(),
						notifyUrl, rechargeInfoReq.getBankCode());
			case HX_PAYMENT:
				notifyUrl = hxPayConfig.getNotifyUrl()
						.replace("{origin}", scheme)
						.replace("{alias}", domain);
				return sendPay(amount, record.getId(), ip, channel, rechargeInfoReq.getReturnUrl(),
						notifyUrl, rechargeInfoReq.getBankCode());
			case FK_PAYMENT:
				notifyUrl = fkPayConfig.getNotifyUrl()
						.replace("{origin}", scheme)
						.replace("{alias}", domain);
				return sendPay(amount, record.getId(), ipStr, channel, rechargeInfoReq.getReturnUrl(),
						notifyUrl, rechargeInfoReq.getBankCode());
			default:
				throw new ServiceException("支付渠道错误");
		}
	}
	private Object appChannel(BackPaymentChannel channel, String scheme, String domain, BigDecimal amount, Record record, RechargeInfoReq rechargeInfoReq, String ipStr) {
		String notifyUrl;
		if (channel.getApiType() == PayApiType.FK_PAYMENT) {
			notifyUrl = fkPayConfig.getNotifyUrl()
					.replace("{origin}", scheme)
					.replace("{alias}", domain);
			return sendPayApp(amount, record.getId(), ipStr, channel,
					notifyUrl, rechargeInfoReq.getBankCode());
		}
		throw new ServiceException("支付渠道错误");
	}

	/**
	 * 校验是否充值成功
	 *
	 * @param id 编号
	 * @return 返回结果
	 * @author jzw
	 * @since 2022/1/27 14:47
	 */
	@Override
	public boolean checkRecharge(Serializable id) {
		LambdaQueryWrapper<Record> query = Wrappers.lambdaQuery();
		query.eq(Record::getId, id).eq(Record::getState, TransactionState.COMPLETED);
		return recordMapper.selectOne(query) != null;
	}

	@Override
	public Map<String, Object> recordList(long userId, TransactionRecordQueryReq req) {
		Page<TransactionDetail> page = Page.of(req.getCurrent(), req.getSize());
		page = fundDetailMapper.detailList(page, userId, req);
		List<Long> orderIds = page.getRecords().stream().map(TransactionDetail::getOrderId).collect(Collectors.toList());
		Map<Long, OrderRate> map = new HashMap<>();
		if (CollectionUtil.isNotEmpty(orderIds)) {
			List<OrderRate> rates = orderRateMapper.selectList(Wrappers.<OrderRate>lambdaQuery().in(OrderRate::getId, orderIds));
			map = rates.stream().collect(Collectors.toMap(OrderRate::getId, rate -> rate));
		}
		Map<Long, OrderRate> temp = map;
		return PageUtil.formatPage(page, d -> format(d, temp));
	}

	@Override
	public Map<String, Object> recordList1(long userId, TransactionRecordQueryReq req) {
		Page<TransactionDetail> page = Page.of(req.getCurrent(), req.getSize());
		page = fundDetailMapper.detailList2(page, userId, req);
		List<Long> orderIds = page.getRecords().stream().map(TransactionDetail::getOrderId).collect(Collectors.toList());
		Map<Long, OrderRate> map = new HashMap<>();
		if (CollectionUtil.isNotEmpty(orderIds)) {
			List<OrderRate> rates = orderRateMapper.selectList(Wrappers.<OrderRate>lambdaQuery().in(OrderRate::getId, orderIds));
			map = rates.stream().collect(Collectors.toMap(OrderRate::getId, rate -> rate));
		}
		Map<Long, OrderRate> temp = map;
		return PageUtil.formatPage(page, d -> format(d, temp));
	}

	@Override
	public Map<String, Object> recordList2(long userId, TransactionRecordQueryReq req) {
		Page<TransactionDetail> page = Page.of(req.getCurrent(), req.getSize());
		page = fundDetailMapper.detailList3(page, userId, req);
		List<Long> orderIds = page.getRecords().stream().map(TransactionDetail::getOrderId).collect(Collectors.toList());
		Map<Long, OrderRate> map = new HashMap<>();
		if (CollectionUtil.isNotEmpty(orderIds)) {
			List<OrderRate> rates = orderRateMapper.selectList(Wrappers.<OrderRate>lambdaQuery().in(OrderRate::getId, orderIds));
			map = rates.stream().collect(Collectors.toMap(OrderRate::getId, rate -> rate));
		}
		Map<Long, OrderRate> temp = map;
		return PageUtil.formatPage(page, d -> format(d, temp));
	}

	private Map<String, Object> format(TransactionDetail detail, Map<Long, OrderRate> map) {
		List<String> types = StrUtil.split(detail.getPaySolution(), "#");
		OrderRate rate = map.get(detail.getOrderId());
		return Putif.start()
				.anyway("id", detail.getId())
				.anyway("orderId", detail.getOrderId())
				.anyway("time", detail.getDealTime())
				.anyway("amount", detail.getAmount())
				.anyway("balance", detail.getBalance())
				.anyway("type", detail.getType())
				.anyway("partition", detail.getPartitionName())
				.anyway("goods", detail.getGoods())
				.anyway("ip", detail.getIp())
				.anyway("goodsType", detail.getGoodsType() != null ? detail.getGoodsType().description() : "")
				.cond(detail.getType() == 0, m -> m.anyway("buyerAccount", detail.getBuyerAccount()))
				//.anyway("buyerAccount", detail.getBuyerAccount())
				.anyway("alias", detail.getAlias())
				.anyway("paySolution", types.isEmpty() ? "" : types.get(0))
				.cond(detail.getType() == 1,
						m -> m.anyway("income", detail.getSellerShare()))
				.cond((detail.getType() == 1 || detail.getType() == 3) && (rate != null && (!rate.getRateType() || !rate.getSingleType())),
						m -> m.anyway("royalties", detail.getMerchantShare().add(detail.getAgencyShare()).add(detail.getPlatformShare())))
				.get();
	}

	private void setOrderFrozenAmount(BigDecimal sellerShare, Order order, int hour) {
		LocalDateTime now = LocalDateTime.now();
		OrderFrozenAmount frozenAmount = new OrderFrozenAmount();
		frozenAmount.setAmount(sellerShare);
		frozenAmount.setOrderId(order.getId());
		frozenAmount.setUserId(order.getSellerId());
		frozenAmount.setCreateTime(now);
		frozenAmount.setDeadTime(now.plusHours(hour));

		boolean success = orderFrozenAmountService.save(frozenAmount);
		if (!success) {
			log.warn("写入转化表失败, {}", order);
			throw new ServiceException("购买失败");
		}
	}

	@Override
	public boolean receive(long orderId, String account) {
		Order order = orderMapper.selectById(orderId);
		if (order == null) {
			return false;
		}
		if (order.getStatus() != OrderStatus.COMPLETED) {
			return false;
		}
		if (order.getReceived()) {
			return false;
		}
		Order update = new Order();
		update.setId(order.getId());
		if (StrUtil.isNotBlank(account) && !StrUtil.equals(order.getBuyerAccount(), account)) {
			update.setBuyerAccount(account);
		}
		update.setReceived(true);
		long count = orderMapper.updateById(update);
		if (count < 1) {
			return false;
		}
		//给消息队列发送购买成功的消息
		try {
			messageUtil.sendMessage(OrderMsg.TOPIC, OrderMsg.TAG, OrderMsg.start().id(order.getId()).time(LocalDateTime.now()).status(OrderStatus.COMPLETED));
		} catch (Exception e) {
			log.warn("发送购买消息错误 {}", order, e);
		}
		return true;
	}

	@Override
	public void receive(long orderId) {
		Order update = new Order();
		update.setId(orderId);
		update.setReceived(true);
		long count = orderMapper.updateById(update);
		if (count > 0) { //给消息队列发送购买成功的消息
			try {
				messageUtil.sendMessage(OrderMsg.TOPIC, OrderMsg.TAG, OrderMsg.start().id(orderId).time(LocalDateTime.now()).status(OrderStatus.COMPLETED));
			} catch (Exception e) {
				log.warn("发送购买消息错误 {}", orderId, e);
			}
		}
	}

	@Override
	public BigDecimal todayRecord(long userId) {
		String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
		String begin = today + " 00:00:00";
		String end = today + " 23:59:59";
		List<Record> records = recordMapper.selectList(Wrappers.<Record>lambdaQuery()
				.eq(Record::getPlayerId, userId)
				.between(Record::getUpdateTime, begin, end));
		return records.stream().map(Record::getAmount).reduce(BigDecimal.ZERO, BigDecimal::add);
	}

	private void receive(Order order) {
		Order update = new Order();
		update.setId(order.getId());
		update.setReceived(true);
		long count = orderMapper.updateById(update);
		if (count > 0) { //给消息队列发送购买成功的消息
			try {
				messageUtil.sendMessage(OrderMsg.TOPIC, OrderMsg.TAG, OrderMsg.start().id(order.getId()).time(LocalDateTime.now()).status(OrderStatus.COMPLETED));
			} catch (Exception e) {
				log.warn("发送购买消息错误 {}", order, e);
			}
		}
	}

	private FundDetail createFundDetail(FrontUser user, Order order, BigDecimal merchantShare, BigDecimal platformShare, BigDecimal sellerShare, LocalDateTime now) {
		FundDetail detail = new FundDetail();
		if (user != null) {
			detail.setPlayerAccount(user.getAccount());
			detail.setPlayerId(user.getId());
		}
		detail.setGoods(order.getGoods());
		detail.setAmount(order.getAmount());
		detail.setMerchantShare(merchantShare);
		detail.setAgencyShare(new BigDecimal("0"));
		detail.setPlatformShare(platformShare);
		detail.setSellerShare(sellerShare);
		detail.setOrderId(order.getId());
		detail.setDealTime(now);
		detail.setUid(order.getUid());
		return detail;
	}

	private Order checkOrder(long orderId, long userId) {
		LambdaQueryWrapper<Order> query = Wrappers.lambdaQuery();
		query.eq(Order::getId, orderId).eq(Order::getStatus, OrderStatus.PASSED);
		Order order = orderMapper.selectOne(query);
		if (order == null) {
			throw new ServiceException("当前商品不存在");
		}
		if (order.getSellerId() == userId) {
			throw new ServiceException("不能购买自己的商品");
		}
		return order;
	}

	private List<Order> checkOrder(List<Long> ids, long userId) {
		LambdaQueryWrapper<Order> query = Wrappers.lambdaQuery();
		query.in(Order::getId, ids).eq(Order::getStatus, OrderStatus.PASSED);
		List<Order> orders = orderMapper.selectList(query);
		if (CollectionUtil.isEmpty(orders)) {
			throw new ServiceException("当前商品不存在");
		}
		orders = orders.stream().filter(o -> o.getSellerId() != userId).collect(Collectors.toList());
		if (CollectionUtil.isEmpty(orders)) {
			throw new ServiceException("不能购买自己的商品");
		}
		return orders;
	}

	private Object sendPay(BigDecimal amount, Long outTradeNo, long ip, BackPaymentChannel channel, String returnUrl, String notifyUrl, String bankCode) {
		Dict dict = Dict.create();
		//价格*100 转换成分
		dict.set("amount", NumberUtil.mul(amount, 100)).
				set("out_trade_no", outTradeNo).
				set("client_ip", ip).
				set("merchant_id", channel.getMerchantNumber()).
				set("notify_url", notifyUrl).
				set("channel_type", channel.getType().getKey()).
				set("return_url", returnUrl);
		if (channel.getType() == RechargeSolution.UPAY_PAYMENT) {
			dict.set("bank_code", bankCode);
		}
		String code = PayUtil.getSignToken(dict, channel.getSecret());
		log.info("支付渠道：{}, 回调地址：{} 支付接口返回code：{}", channel, notifyUrl, code);
		dict.set("code", code);
		// 创建支付 订单
		String url;
		switch (channel.getApiType()) {
			case DY_PAYMENT:
				url = payConfig.getUrl();
				break;
			case HX_PAYMENT:
				url = hxPayConfig.getUrl();
				break;
			default:
				throw new ServiceException("支付渠道错误");
		}
		String post = HttpUtil.post(url, dict);
		log.info("支付渠道：{}, 支付地址：{}， 支付接口返回结果：{}", channel, url, post);
		JSONObject map = JSONUtil.parseObj(post);
		int apiCode = (int) map.get("api_code");
		log.info("\n支付渠道：{},\n支付地址：{}，\n支付参数：{}，\n支付接口返回结果：{}", channel, url, dict, post);
		if (apiCode != 200) {
			throw new ServiceException((String) map.get("api_msg"));
		}
		else {
			return map.get("api_msg");
		}
	}

	private Object sendPay(BigDecimal amount, Long outTradeNo, String ip, BackPaymentChannel channel, String returnUrl, String notifyUrl, String bankCode) {
		Dict dict = Dict.create();
		//价格*100 转换成分
		dict.set("amount", NumberUtil.mul(amount, 100).intValue())
				.set("requestId", outTradeNo)
				.set("clientIp", ip)
				.set("merchantId", channel.getMerchantNumber())
				.set("notifyUrl", notifyUrl)
				.set("channelType", channel.getType().getKey())
				.set("returnUrl", returnUrl)
				.set("gameName", "好交易商户平台");
		if (channel.getType() == RechargeSolution.UPAY_PAYMENT) {
			dict.set("bankCode", bankCode);
		}
		String code = PayUtil.getSignToken(dict, channel.getSecret());
		dict.set("code", code);
		log.info("支付渠道：{}, 支付接口返回code：{}", channel, code);
		// 创建支付 订单
		log.info("nPayConfig={}", fkPayConfig);
		String url = fkPayConfig.getUrl();
		String json = JSONUtil.toJsonStr(dict);
		String post = HttpUtil.post(url, json);
		log.info("支付渠道：{},\n支付地址：{}，\n回调地址：{}\n支付参数：{}，\n支付接口返回结果：{}", channel, url, notifyUrl, dict, post);
		JSONObject map = JSONUtil.parseObj(post);
		int apiCode = (int) map.get("code");
		if (apiCode != 10000) {
			throw new ServiceException((String) map.get("msg"));
		}
		else {
			return map.get("data");
		}
	}

	private Object sendPayApp(BigDecimal amount, Long outTradeNo, String ip, BackPaymentChannel channel, String notifyUrl, String bankCode) {
		Dict dict = Dict.create();
		//价格*100 转换成分
		dict.set("amount", NumberUtil.mul(amount, 100).intValue())
				.set("requestId", outTradeNo)
				.set("merchantId", channel.getMerchantNumber())
				.set("clientIp", ip)
				.set("notifyUrl", notifyUrl)
				.set("channelType", channel.getType().getKey())
				.set("gameName", "好交易商户app");
		String code = PayUtil.getSignToken(dict, channel.getSecret());
		dict.set("code", code);
		log.info("支付渠道：{}, 支付接口返回code：{}", channel, code);
		// 创建支付 订单
		log.info("fkPayConfig={}", fkPayConfig);
		String url = fkPayConfig.getAppUrl();
		String json = JSONUtil.toJsonStr(dict);
		String post = HttpUtil.post(url, json);
		log.info("支付渠道：{},\n支付地址：{}，\n回调地址：{}\n支付参数：{}，\n支付接口返回结果：{}", channel, url, notifyUrl, dict, post);
		JSONObject map = JSONUtil.parseObj(post);
		int apiCode = (int) map.get("code");
		if (apiCode != 10000) {
			throw new ServiceException((String) map.get("msg"));
		}
		else {
			JSONObject data = map.get("data", JSONObject.class);
			data.putOpt("id", outTradeNo);
			return data;
		}
	}

	private void processOrder(Order order, String channelName, FrontUser buyer, FrontUser seller, BigDecimal channelShare, String alias, boolean isScan) {
		ServiceCharge charge = getCharge(order);
		MerchantUser merchantUser = merchantUserMapper.selectById(order.getUid());
		BigDecimal platformShare, merchantShare, sellerShare;
		BigDecimal conversionUnit = BigDecimal.valueOf(100);
		BigDecimal p_rate = NumberUtil.div(charge.getChargeRate (), conversionUnit); //平台浮动费率
		BigDecimal m_rage = NumberUtil.div(charge.getRateAdditional(), conversionUnit); //商户浮动费率
		if (charge.getSingleType() && charge.getRateType()) {//方式一，全从商户扣
			sellerShare = order.getAmount();
			platformShare = NumberUtil.mul(order.getAmount(), p_rate).add(charge.getChargeSingle());
			merchantShare = BigDecimal.ZERO;
		} else if (!charge.getRateType() && !charge.getSingleType()) { //方式二，全从玩家扣
			merchantShare =  NumberUtil.mul(order.getAmount(), m_rage).add(charge.getSingleAdditional());
			platformShare = NumberUtil.mul(order.getAmount(), p_rate).add(charge.getChargeSingle());
			sellerShare = order.getAmount().subtract(merchantShare).subtract(platformShare);
		} else if (!charge.getRateType() && charge.getSingleType()) { //方式三，金额从玩家口，单笔从商户扣
			BigDecimal m_charge = NumberUtil.mul(order.getAmount(), m_rage); //商户浮动收益
			BigDecimal p_charge = NumberUtil.mul(order.getAmount(), p_rate); //平台基本收益
			sellerShare = order.getAmount().subtract(m_charge).subtract(p_charge);
			platformShare = p_charge.add(charge.getChargeSingle());
			merchantShare = m_charge;
		} else {//方式三，金额从商户扣，单笔从玩家扣
			BigDecimal p_charge = NumberUtil.mul(order.getAmount(), p_rate); //平台基本收益
			sellerShare = order.getAmount().subtract(charge.getSingleAdditional()).subtract(charge.getChargeSingle());
			platformShare = p_charge.add(charge.getChargeSingle());
			merchantShare = charge.getSingleAdditional();
		}
		log.info("订单 {} 的结算信息：{}，总价：{}，平台收益：{}，商户收益：{}，玩家收益：{}", order, charge, order.getAmount(), platformShare, merchantShare, sellerShare);
		LocalDateTime now = LocalDateTime.now();
		//商户添加余额
		MerchantUser newMerchantUser = new MerchantUser();
		newMerchantUser.setId(merchantUser.getId());
		LambdaUpdateWrapper<MerchantUser> updateWrapper = Wrappers.lambdaUpdate(newMerchantUser);
		updateWrapper.setSql("balance = balance + " + merchantShare);
		merchantUserMapper.update(newMerchantUser, updateWrapper);
		//添加交易记录
		FundDetail buyDetail = createFundDetail(buyer, order, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO, now);
		buyDetail.setType(0);
		if (isScan) {
			buyDetail.setBalance(buyer.getBalance());
		} else {
			BigDecimal balance = buyer.getBalance().subtract(order.getAmount());
			buyer.setBalance(balance);
			buyDetail.setBalance(balance);
		}
		buyDetail.setIp(order.getBuyerIp());
		buyDetail.setPaySolution(channelName);
		buyDetail.setChannelShare(channelShare);
		buyDetail.setAlias(alias);
		fundDetailMapper.insert(buyDetail);
		FundDetail sellDetail = createFundDetail(seller, order, merchantShare, platformShare, sellerShare, now);
		sellDetail.setType(1);
		sellDetail.setBalance(seller.getBalance().add(sellerShare));
		sellDetail.setPaySolution(channelName);
		sellDetail.setAlias(alias);
		fundDetailMapper.insert(sellDetail);
		BackSetting setting = backSettingService.findByKey(SystemSettingConstant.CONVERT_TIME);
		if (setting != null && NumberUtil.parseInt(setting.getValue()) > 0) {
			setOrderFrozenAmount(sellerShare, order, NumberUtil.parseInt(setting.getValue()));
		} else {
			FrontUser updateUser = new FrontUser();
			updateUser.setId(order.getSellerId());
			LambdaUpdateWrapper<FrontUser> updates = Wrappers.<FrontUser>lambdaUpdate()
					.setSql("balance = balance + " + sellerShare)
					.eq(FrontUser::getId, order.getSellerId());
			int count = frontUserMapper.update(updateUser, updates);
			if (count < 1) {
				log.warn("更新卖家余额失败, {}", order);
				throw new ServiceException("购买失败");
			}
		}
	}

	private ServiceCharge getCharge(Order order) {
		ServiceCharge charge = new ServiceCharge();
		OrderRate rate = orderRateMapper.selectById(order.getId());
		if (rate == null) {
			MerchantRate r = merchantRateMapper.selectById(order.getUid());
			charge.setChargeRate(r.getChargeRate());
			charge.setChargeSingle(r.getChargeSingle());
			charge.setRateAdditional(r.getRateAdditional());
			charge.setSingleAdditional(r.getSingleAdditional());
			charge.setRateType(r.getRateType());
			charge.setSingleType(r.getSingleType());
			log.info("订单费率为空，应用商户默认费率：{}, 获取的费率信息：{}", r, charge);
			return charge;
		}
		charge.setChargeRate(rate.getChargeRate());
		charge.setChargeSingle(rate.getChargeSingle());
		charge.setRateAdditional(rate.getRateAdditional());
		charge.setSingleAdditional(rate.getSingleAdditional());
		charge.setRateType(rate.getRateType());
		charge.setSingleType(rate.getSingleType());
		log.info("订单费率：{}, 获取的费率信息：{}", rate, charge);
		return charge;
	}
}

